home *** CD-ROM | disk | FTP | other *** search
/ Mac Mania 5 / MacMania 5.toast / / Internet software / NewsWatcher / NW Source / Source / news.c < prev    next >
Text File  |  1997-01-10  |  44KB  |  1,620 lines

  1. /*----------------------------------------------------------------------------
  2.  
  3.     news.c
  4.  
  5.     This module handles all transactions with the NNTP server. It acts as
  6.     an interface between the rest of NewsWatcher and the resuable nntp.c
  7.     module.
  8.     
  9.     Copyright © 1994-1997, Northwestern University.
  10.  
  11. ----------------------------------------------------------------------------*/
  12.  
  13. #include <stdlib.h>
  14. #include <stdio.h>
  15. #include <string.h>
  16.  
  17. #include "glob.h"
  18. #include "dialog.h"
  19. #include "news.h"
  20. #include "nntp.h"
  21. #include "status.h"
  22. #include "newswatcher.h"
  23. #include "charset.h"
  24. #include "text.h"
  25. #include "strutil.h"
  26. #include "memutil.h"
  27. #include "cache.h"
  28. #include "windutil.h"
  29. #include "resutil.h"
  30. #include "fileutil.h"
  31. #include "ic.h"
  32.  
  33.  
  34.  
  35. #define kPostAuthDlg            136
  36. #define kStartupAuthDlg            137
  37. #define kAuthUsername            5
  38. #define kAuthPassword            7
  39.  
  40. #define kFileBufLen                (10*1024)
  41.  
  42.  
  43.  
  44. typedef struct TTruncateAttachedFileInfo {
  45.     long pos;                            /* current position in text received so far */
  46.     Boolean flagReqd;                    /* true if "flag" line required */
  47. } TTruncateAttachedFileInfo;
  48.  
  49. typedef struct TCopyArticleToFileInfo {
  50.     short fileRefNum;                    /* file ref num */
  51.     Ptr fileBuf;                        /* pointer to output buffer */
  52.     Ptr fileBufAux;                        /* pointer to auxiliary output buffer */
  53.     long fileBufAuxSize;                /* number of bytes in aux buffer */
  54.     long fileBufPos;                    /* current position in output buffer */
  55.     long fileLinePos;                    /* position in current line */
  56.     char fileLastChar;                    /* last char */
  57.     Boolean fileTruncate;                /* true to truncate if attached file */
  58.     Boolean fileTruncated;                /* true if attached file found, should truncate */
  59.     TAttachedFileKind fileKind;            /* kind of attached file */
  60.     Boolean flagReqd;                    /* true if attached file must include "begin" flag line */
  61. } TCopyArticleToFileInfo;
  62.  
  63.  
  64.  
  65. static NntpStreamRef gNewsStream = nil;    /* reference to news server NNTP stream */
  66.  
  67.  
  68.  
  69. /*----------------------------------------------------------------------------
  70.     IsLegalBinHexLine 
  71.     
  72.     Check a line to see if it contains legal BinHex encoded text.
  73.     
  74.     Entry:    p = pointer to line.
  75.             len = length of line.
  76.             
  77.     Exit:    function result = true if legal BinHex.
  78. ----------------------------------------------------------------------------*/
  79.  
  80. static Boolean IsLegalBinHexLine (unsigned char *p, long len)
  81. {
  82.     unsigned char *pEnd, *pBegin;
  83.     unsigned char c;
  84.     
  85.     static Boolean legalBinHexChar[] = {
  86.         /* 0x00 */
  87.             false,    false,    false,    false,    false,    false,    false,    false,
  88.             false,    false,    false,    false,    false,    false,    false,    false,
  89.         /* 0x10 */
  90.             false,    false,    false,    false,    false,    false,    false,    false,
  91.             false,    false,    false,    false,    false,    false,    false,    false,
  92.         /* 0x20 */
  93.             false,    true,    true,    true,    true,    true,    true,    true,
  94.             true,    true,    true,    true,    true,    true,    false,    false,
  95.         /* 0x30 */
  96.             true,    true,    true,    true,    true,    true,    true,    false,
  97.             true,    true,    true,    false,    false,    false,    false,    false,
  98.         /* 0x40 */
  99.             true,    true,    true,    true,    true,    true,    true,    true,
  100.             true,    true,    true,    true,    true,    true,    true,    false,
  101.         /* 0x50 */
  102.             true,    true,    true,    true,    true,    true,    true,    false,
  103.             true,    true,    true,    true,    false,    false,    false,    false,
  104.         /* 0x60 */
  105.             true,    true,    true,    true,    true,    true,    true,    false,
  106.             true,    true,    true,    true,    true,    true,    false,    false,
  107.         /* 0x70 */
  108.             true,    true,    true,    false,    false,    false,    false,    false,
  109.             false,    false,    false,    false,    false,    false,    false,    false,
  110.         /* 0x80 */
  111.             false,    false,    false,    false,    false,    false,    false,    false,
  112.             false,    false,    false,    false,    false,    false,    false,    false,
  113.             false,    false,    false,    false,    false,    false,    false,    false,
  114.             false,    false,    false,    false,    false,    false,    false,    false,
  115.             false,    false,    false,    false,    false,    false,    false,    false,
  116.             false,    false,    false,    false,    false,    false,    false,    false,
  117.             false,    false,    false,    false,    false,    false,    false,    false,
  118.             false,    false,    false,    false,    false,    false,    false,    false,
  119.             false,    false,    false,    false,    false,    false,    false,    false,
  120.             false,    false,    false,    false,    false,    false,    false,    false,
  121.             false,    false,    false,    false,    false,    false,    false,    false,
  122.             false,    false,    false,    false,    false,    false,    false,    false,
  123.             false,    false,    false,    false,    false,    false,    false,    false,
  124.             false,    false,    false,    false,    false,    false,    false,    false,
  125.             false,    false,    false,    false,    false,    false,    false,    false,
  126.             false,    false,    false,    false,    false,    false,    false,    false,
  127.         };
  128.  
  129.     for (pBegin = p, pEnd = p + len; p < pEnd; p++)
  130.         if (!legalBinHexChar[*p]) return false;
  131.     c = *pBegin;
  132.     for (p = pBegin+1; p < pEnd; p++)
  133.         if (*p != c) return true;
  134.     return false;
  135. }
  136.  
  137.  
  138.  
  139. /*----------------------------------------------------------------------------
  140.     IsLegalUULine 
  141.     
  142.     Check a line to see if it contains legal uuencode text.
  143.     
  144.     Entry:    p = pointer to line.
  145.             len = length of line.
  146.             
  147.     Exit:    function result = true if legal uuencode.
  148. ----------------------------------------------------------------------------*/
  149.  
  150. static Boolean IsLegalUULine (unsigned char *p, long len)
  151. {
  152.     long n;
  153.     unsigned char *pEnd, *pBegin;
  154.     unsigned char c;
  155.     
  156.     n = *p - ' ';
  157.     if (n < 0 || n > 63) return false;
  158.     n = (n+2)/3*4;
  159.     if (len > n+3 || len < n-3) return false;
  160.     for (pBegin = p, pEnd = p + len, p++; p < pEnd; p++)
  161.         if (*p < ' ' || *p > '`') return false;
  162.     c = *pBegin;
  163.     for (p = pBegin+1; p < pEnd; p++)
  164.         if (*p != c) return true;
  165.     return false;
  166. }
  167.  
  168.  
  169.  
  170. /*----------------------------------------------------------------------------
  171.     CheckForAttachedFile 
  172.     
  173.     Check for BinHex or UUEncode text.
  174.     
  175.     Entry:    t = pointer to text.
  176.             len = length of text.
  177.             flagReqd = true if the encoded text must include a begin "flag" line.
  178.             skipToNextLine = true to skip to beginning of next line.
  179.     
  180.     Exit:    function result = true if BinHex or UUEncode text encountered.
  181.             *pos = offset in text of beginning of BinHex or UUEncode text if
  182.                 function result is true, else offset in text of location where
  183.                 search should be resumed when more text has arrived from the
  184.                 server.
  185.                 
  186.     The text can have either CR or CRLF line terminators.
  187.                 
  188.     This function looks for the flag line (if required), followed by 
  189.     two adjacent lines of the same length at least 60 characters long,
  190.     each of which is either a legal BinHex line or a legal uuencode line.
  191. ----------------------------------------------------------------------------*/
  192.  
  193. static Boolean CheckForAttachedFile (Ptr t, long len, Boolean flagReqd, 
  194.     Boolean skipToNextLine, long *pos)
  195. {
  196.     long lenA, lenB;
  197.     unsigned char *p, *pEnd, *q, *r;
  198.     unsigned char *flagLine, *lineA, *lineB, *nextLine;
  199.     Boolean haveBeginFlagLine, beginFlagLineIsBinHex, foundIt;
  200.  
  201.     p = (unsigned char*)t;
  202.     pEnd = p + len - 1;
  203.     
  204.     if (skipToNextLine) {
  205.         while (p < pEnd && *p != CR) p++;
  206.         while (p < pEnd && (*p == CR || *p == LF)) p++;
  207.         if (p >= pEnd) return false;
  208.     }
  209.     
  210.     while (p < pEnd) {
  211.         if (flagReqd) {
  212.             haveBeginFlagLine = false;
  213.             flagLine = p;
  214.             while (flagLine < pEnd) {
  215.                 q = flagLine;
  216.                 while (q < pEnd && *q != CR) q++;
  217.                 while (q < pEnd && (*q == CR || *q == LF)) q++;
  218.                 if (q >= pEnd) break;
  219.                 if (*flagLine == '(') {
  220.                     if (flagLine + 39 >= pEnd) break;
  221.                     if (strncmp((char*)flagLine, 
  222.                         "(This file must be converted with BinHex", 39) == 0) 
  223.                     {
  224.                         haveBeginFlagLine = true;
  225.                         beginFlagLineIsBinHex = true;
  226.                         break;
  227.                     }
  228.                 } else if (*flagLine == 'b') {
  229.                     if (flagLine + 9 >= pEnd) break;
  230.                     if (strncmp((char*)flagLine, "begin ", 6) == 0) {
  231.                         r = flagLine + 6;
  232.                         if (isoctal(*r) && isoctal(*(r+1)) && isoctal(*(r+2))) {
  233.                             haveBeginFlagLine = true;
  234.                             beginFlagLineIsBinHex = false;
  235.                             break;
  236.                         }
  237.                     }
  238.                 } 
  239.                 flagLine = q;
  240.             }
  241.             if (!haveBeginFlagLine) {
  242.                 *pos = (char*)flagLine - t;
  243.                 return false;
  244.             }
  245.             p = flagLine;
  246.             lineA = q;
  247.             if (lineA >= pEnd) break;
  248.         } else {
  249.             lineA = p;
  250.         }
  251.         q = lineA;
  252.         while (q < pEnd && *q != CR) q++;
  253.         lenA = q - lineA;
  254.         while (q < pEnd && (*q == CR || *q == LF)) q++;
  255.         lineB = q;
  256.         if (lineB >= pEnd) break;
  257.         nextLine = flagReqd ? lineA : lineB;
  258.         if (lenA < 60) {
  259.             p = nextLine;
  260.             continue;
  261.         }
  262.         q = lineB;
  263.         while (q < pEnd && *q != CR) q++;
  264.         if (q >= pEnd) break;
  265.         lenB = q - lineB;
  266.         if (lenA != lenB) {
  267.             p = nextLine;
  268.             continue;
  269.         }
  270.         if (flagReqd) {
  271.             if (beginFlagLineIsBinHex) {
  272.                 foundIt = IsLegalBinHexLine(lineA, lenA) && IsLegalBinHexLine(lineB, lenB);
  273.             } else {
  274.                 foundIt = IsLegalUULine(lineA, lenA) && IsLegalUULine(lineB, lenB);
  275.             }
  276.         } else {
  277.             foundIt = (IsLegalBinHexLine(lineA, lenA) && IsLegalBinHexLine(lineB, lenB)) ||
  278.                 (IsLegalUULine(lineA, lenA) && IsLegalUULine(lineB, lenB));
  279.         }
  280.         if (foundIt) {
  281.             *pos = (char*)lineA - t;
  282.             return true;
  283.         } else {
  284.             p = nextLine;
  285.             continue;
  286.         }
  287.     }
  288.     
  289.     *pos = (char*)p - t;
  290.     return false;
  291. }
  292.  
  293.  
  294.  
  295. /*----------------------------------------------------------------------------
  296.     TruncateAttachedFile 
  297.     
  298.     Check for attached file and truncate article if BinHex or uuencode
  299.     text encountered.
  300.     
  301.     Entry:    t = pointer to raw text received from server so far.
  302.             tLen = length of text received from server so far.
  303.             *userDataPtr = pointer to TTruncateAttachedFileInfo struct.
  304.             
  305.     Exit:    function result = error code = netTruncatedErr if attached
  306.                 file discovered.
  307.             *truncPos = position at which text should be truncated
  308.                 if error code = netTruncatedErr.
  309. ----------------------------------------------------------------------------*/
  310.  
  311. static OSErr TruncateAttachedFile (Ptr t, long tLen, Ptr userDataPtr,
  312.     long *truncPos)
  313. {
  314.     TTruncateAttachedFileInfo *x;
  315.     Boolean flagReqd;
  316.     long pos;
  317.     Boolean foundIt;
  318.  
  319.     x = (TTruncateAttachedFileInfo*)userDataPtr;
  320.     flagReqd = x->flagReqd;
  321.     pos = x->pos;
  322.     if (pos >= tLen) return noErr;
  323.     
  324.     foundIt = CheckForAttachedFile(t + pos, tLen - pos, flagReqd, false, &pos);
  325.     
  326.     pos += x->pos;
  327.     if (foundIt) {
  328.         *truncPos = pos;
  329.         return netTruncatedErr;
  330.     } else {
  331.         x->pos = pos;
  332.         return noErr;
  333.     }
  334. }
  335.  
  336.  
  337.  
  338. /*----------------------------------------------------------------------------
  339.     NetworkError 
  340.     
  341.     Process a network error.
  342.     
  343.     Entry:    stream = reference to NNTP stream.
  344.             host = address of news server, or nil if default server.
  345.             err = error code as returned by function in nntp.c.
  346.     
  347.     Entry:    function result = error code.
  348.     
  349.     If the error is a server error, the error is reported to the user,
  350.     and the function result is userCanceledErr. Otherwise, the error
  351.     code is returned as is, and it is the responsibility of the calling
  352.     code to report it.
  353. ----------------------------------------------------------------------------*/
  354.  
  355. static OSErr NetworkError (NntpStreamRef stream, char *host, OSErr err)
  356. {
  357.     NetServerErrInfo serverErrInfo;
  358.  
  359.     if (err == nntpServerErr) {
  360.         NntpGetServerErrInfo(stream, &serverErrInfo);
  361.         err = ServerErrorMessage(kStrNews, serverErrInfo.command, serverErrInfo.response);
  362.         if (err != noErr) return err;
  363.         return userCanceledErr;
  364.     } else {
  365.         if (host == nil) {
  366.             p2cstr(gPrefs.newsServerName);
  367.             SaveNetErrorInfo(kStrNews, (char*)gPrefs.newsServerName);
  368.             c2pstr((char*)gPrefs.newsServerName);
  369.         } else {
  370.             SaveNetErrorInfo(kStrNews, host);
  371.         }
  372.         return err;
  373.     }
  374. }
  375.  
  376.  
  377.  
  378. /*----------------------------------------------------------------------------
  379.     SetServerOptions 
  380.     
  381.     Set news server options from preferences.
  382.     
  383.     Exit:    *options = current server options.
  384. ----------------------------------------------------------------------------*/
  385.  
  386. static void SetServerOptions (NntpStreamOptions *options)
  387. {
  388.     MyICReadSharedPrefs(kICNewsAuthUsername);
  389.     MyICReadSharedPrefs(kICNewsAuthPassword);
  390.  
  391.     options->idleTime = 10;
  392.     options->useXPAT = gPrefs.useXPAT;
  393.     options->sendModeReader = !gPrefs.noModeReader;
  394.     options->batchedCmds = gPrefs.batchedGroupCmds;
  395.     options->newConnection = !gPrefs.noNewConnection;
  396.     options->authOnConnect = gPrefs.authAtStartup;
  397.     strcpy(options->username, gPrefs.authUsername);
  398.     strcpy(options->password, gPrefs.authPassword);
  399. }
  400.  
  401.  
  402.  
  403. /*----------------------------------------------------------------------------
  404.     GetAuthInfo 
  405.     
  406.     Get authorization information.
  407.  
  408.     Entry:    dlgID = dialog id.
  409.             statusMsg = status message, C-format, or nil if startup call.
  410.  
  411.     Exit:    function result = error code.
  412.             gPrefs.authUsername = authorization username.
  413.             gPrefs.authPassword = authorization password.
  414. ----------------------------------------------------------------------------*/
  415.  
  416. static OSErr GetAuthInfo (short dlgID, char *statusMsg)
  417. {
  418.     DialogPtr dlg = nil;
  419.     short item;
  420.     CStr255 tempStr;
  421.     short len;
  422.     char username[32];
  423.     char password[32];
  424.     OSErr err = noErr;
  425.  
  426.     MyICReadSharedPrefs(kICNewsAuthUsername);
  427.     MyICReadSharedPrefs(kICNewsAuthPassword);
  428.     
  429.     if (*gPrefs.authUsername != 0 && *gPrefs.authPassword != 0) return noErr;
  430.     
  431.     err = MyGetNewDialog(dlgID, ok, cancel, &dlg);
  432.     if (err != noErr) return err;
  433.     RestoreMovableModalDialogPosition(dlg, gPrefs.authLoc);
  434.     strcpy(username, gPrefs.authUsername);
  435.     DlgSetCString(dlg, kAuthUsername, username);
  436.     SetItemUSAsciiNoBlank(dlg, kAuthUsername);
  437.     SetItemMaxLength(dlg, kAuthUsername, 31);
  438.     strcpy(password, gPrefs.authPassword);
  439.     len = strlen(password);
  440.     memset(tempStr, '•', len);
  441.     tempStr[len] = 0;
  442.     DlgSetCString(dlg, kAuthPassword, tempStr);
  443.     SetItemPassword(dlg, kAuthPassword, password);
  444.     SetItemMaxLength(dlg, kAuthPassword, 31);
  445.     if (*username == 0) {
  446.         SelectDialogItemText(dlg, kAuthUsername, 0, 0);
  447.     } else if (*password == 0) {
  448.         SelectDialogItemText(dlg, kAuthPassword, 0, 0);
  449.     } else {
  450.         SelectDialogItemText(dlg, kAuthUsername, 0, kMaxShort);
  451.     }
  452.     
  453.     do {
  454.         DlgEnableItem(dlg, ok, *username != 0 && *password != 0);
  455.         MyMovableModalDialog(dlg, DialogFilter, &item);
  456.         if (item == kAuthUsername) DlgGetCString(dlg, item, username);
  457.     } while (item != ok && item != cancel);
  458.     
  459.     if (item == ok) {
  460.         strcpy(gPrefs.authUsername, username);
  461.         strcpy(gPrefs.authPassword, password);
  462.         MyICWriteSharedPrefs(kICNewsAuthUsername);
  463.         MyICWriteSharedPrefs(kICNewsAuthPassword);
  464.     }
  465.     SaveMovableModalDialogPosition(dlg, &gPrefs.authLoc);
  466.     err = DoClose(dlg);
  467.     if (err != noErr) return err;
  468.     if (item == ok && statusMsg != nil) {
  469.         err = DisplayStatusMessage(statusMsg);
  470.         if (err != noErr) return err;
  471.     }
  472.     return item == ok ? noErr : userCanceledErr;
  473. }
  474.  
  475.  
  476.  
  477. /*----------------------------------------------------------------------------
  478.     TextHasAttachedFile 
  479.     
  480.     Check article text to see if it includes an attached file.
  481.             
  482.     Entry:    text = pointer to article text.
  483.             length = length of text.
  484.     
  485.     Exit:    *fileKind = kind of attached file.
  486. ----------------------------------------------------------------------------*/
  487.  
  488. static void TextHasAttachedFile (Ptr text, long length, TAttachedFileKind *fileKind)
  489. {
  490.     char *p, *pEnd, *q;
  491.     
  492.     for (p = text, pEnd = p + length; p < pEnd; p++) {
  493.         if (*p == CR) {
  494.             q = p+1;
  495.             if (q >= pEnd) break;
  496.             if (*q == '(') {
  497.                 if (strncmp(q, "(This file must be converted with BinHex", 39) == 0) {
  498.                     *fileKind = kBinHex;
  499.                     return;
  500.                 }
  501.             } else if (*q == 'b') {
  502.                 if (strncmp(q, "begin ", 6) == 0) {
  503.                     q += 6;
  504.                     if (q >= pEnd) break;
  505.                     if (isoctal(*q) && isoctal(*(q+1)) && isoctal(*(q+2))) {
  506.                         *fileKind = kUUEncode;
  507.                         return;
  508.                     }
  509.                 }
  510.             }
  511.         }
  512.     }
  513.     *fileKind = kNoAttachedFile;
  514. }
  515.  
  516.  
  517.  
  518. /*----------------------------------------------------------------------------
  519.     FlushFileBuf 
  520.     
  521.     Flush the file buffer.
  522.     
  523.     Entry:    len = length of buffer.
  524.     
  525.     Exit:    function result = error code.
  526. ----------------------------------------------------------------------------*/
  527.  
  528. static OSErr FlushFileBuf (long len, TCopyArticleToFileInfo *x)
  529. {
  530.     OSErr err = noErr;
  531.     long headLen, tailLen, pos;
  532.     
  533.     MapLatin1ToMacPtr(x->fileBuf, len);
  534.     
  535.     if (x->fileTruncate) {
  536.     
  537.         if (x->fileBufAuxSize > 0) {
  538.             headLen = len > 200 ? 200 : len;
  539.             BlockMoveData(x->fileBuf, x->fileBufAux + x->fileBufAuxSize, headLen);
  540.             x->fileBufAuxSize += headLen;
  541.             if (CheckForAttachedFile(x->fileBufAux, x->fileBufAuxSize, x->flagReqd, true, &pos)) {
  542.                 if (pos > 0) {
  543.                     err = MyFSWriteNoCache(x->fileRefNum, &pos, x->fileBufAux, GiveTime);
  544.                     if (err != noErr) return err;
  545.                 }
  546.                 x->fileTruncated = true;
  547.                 return noErr;
  548.             }
  549.             x->fileBufAuxSize -= headLen;
  550.             err = MyFSWriteNoCache(x->fileRefNum, &x->fileBufAuxSize, x->fileBufAux, GiveTime);
  551.             if (err != noErr) return err;
  552.         }
  553.         if (CheckForAttachedFile(x->fileBuf, len, x->flagReqd, true, &pos)) {
  554.             if (pos > 0) {
  555.                 err = MyFSWriteNoCache(x->fileRefNum, &pos, x->fileBuf, GiveTime);
  556.                 if (err != noErr) return err;
  557.             }
  558.             x->fileTruncated = true;
  559.             return noErr;
  560.         }
  561.         tailLen = len > 200 ? 200 : len;
  562.         len -= tailLen;
  563.         BlockMoveData(x->fileBuf + len, x->fileBufAux, tailLen);
  564.         x->fileBufAuxSize = tailLen;
  565.         if (len > 0) {
  566.             err = MyFSWriteNoCache(x->fileRefNum, &len, x->fileBuf, GiveTime);
  567.             if (err != noErr) return err;
  568.         }
  569.     
  570.     } else {
  571.  
  572.         err = MyFSWriteNoCache(x->fileRefNum, &len, x->fileBuf, GiveTime);
  573.         if (err != noErr) return err;
  574.         if (x->fileKind == kNoAttachedFile) {
  575.             if (x->fileBufAuxSize > 0) {
  576.                 headLen = len > 200 ? 200 : len;
  577.                 BlockMoveData(x->fileBuf, x->fileBufAux + x->fileBufAuxSize, headLen);
  578.                 x->fileBufAuxSize += headLen;
  579.                 TextHasAttachedFile(x->fileBufAux, x->fileBufAuxSize, &x->fileKind);
  580.             }
  581.             if (x->fileKind == kNoAttachedFile) 
  582.                 TextHasAttachedFile(x->fileBuf, len, &x->fileKind);
  583.             if (x->fileKind == kNoAttachedFile) {
  584.                 tailLen = len > 200 ? 200 : len;
  585.                 BlockMoveData(x->fileBuf + len - tailLen, x->fileBufAux, tailLen);
  586.                 x->fileBufAuxSize = tailLen;
  587.             }
  588.         }
  589.         
  590.     }
  591.     
  592.     return noErr;
  593. }
  594.  
  595.  
  596.  
  597. /*----------------------------------------------------------------------------
  598.     CopyArticleToFileChunkFunction 
  599.     
  600.     Copy one article chunk to a file.
  601.     
  602.     Entry:    t = pointer to chunk.
  603.             tLen = length of chunk.
  604.             userDataPtr = pointer to TCopyArticleToFileInfo struct.
  605.     
  606.     Exit:    function result = error code.
  607. ----------------------------------------------------------------------------*/
  608.  
  609. static OSErr CopyArticleToFileChunkFunction (Ptr t, long tLen,
  610.     Ptr userDataPtr, long *unused)
  611. {
  612.     register char c, d;
  613.     register Ptr p;
  614.     Ptr pEnd;
  615.     short nSpace;
  616.     OSErr err = noErr;
  617.     TCopyArticleToFileInfo *x;
  618.     
  619.     x = (TCopyArticleToFileInfo*)userDataPtr;
  620.  
  621.     if (tLen == 0) return noErr;
  622.     
  623.     if (x->fileLastChar != 0) {
  624.         c = x->fileLastChar;
  625.     } else { 
  626.         c = *t;
  627.         t++;
  628.         tLen--;
  629.     }
  630.     
  631.     p = x->fileBuf + x->fileBufPos;
  632.     pEnd = x->fileBuf + kFileBufLen - 10;
  633.     
  634.     while (tLen > 0) {
  635.         if (p >= pEnd) {
  636.             err = FlushFileBuf(p - x->fileBuf, x);
  637.             if (err != noErr) return err;
  638.             if (x->fileTruncated) return netTruncatedErr;
  639.             p = x->fileBuf;
  640.         }
  641.         d = *t;
  642.         if (c == BS && d == '_' || c == '_' && d == BS) {
  643.             t++;
  644.             c = *t;
  645.             t++;
  646.             tLen -= 2;
  647.         } else if (c == CR && d == LF) {
  648.             *p++ = CR;
  649.             t++;
  650.             c = *t;
  651.             t++;
  652.             tLen -= 2;
  653.             x->fileLinePos = 0;
  654.         } else if (x->fileLinePos == 0 && c == '.' && d == '.') {
  655.             *p++ = '.';
  656.             t++;
  657.             c = *t;
  658.             t++;
  659.             tLen -= 2;
  660.             x->fileLinePos = 1;
  661.         } else if (c == '\t') {
  662.             nSpace = 8 - (x->fileLinePos % 8);
  663.             while (nSpace--) *p++ = ' ';
  664.             x->fileLinePos += nSpace;
  665.             c = d;
  666.             t++;
  667.             tLen--;
  668.         } else if (c >= ' ' || c == CR || c == FF || c < 0) {
  669.             *p++= c;
  670.             x->fileLinePos++;
  671.             c = d;
  672.             t++;
  673.             tLen--;
  674.         } else {
  675.             c = d;
  676.             t++;
  677.             tLen--;
  678.         }
  679.     }
  680.     
  681.     if (tLen == 0) {
  682.         x->fileLastChar = c;
  683.     } else {
  684.         x->fileLastChar = 0;
  685.     }
  686.     x->fileBufPos = p - x->fileBuf;
  687.     return noErr;
  688. }
  689.  
  690.  
  691.  
  692. /*----------------------------------------------------------------------------
  693.     BuildXPAT 
  694.     
  695.     Build a regular expression string for an XPAT command.
  696.     
  697.     Entry:    pattern = substring for which we are searching.
  698.             regExpLen = max length of regular expression.
  699.  
  700.     Exit:    regExp = regular expression.
  701. ----------------------------------------------------------------------------*/
  702.  
  703. static void BuildXPAT (char *pattern, char *regExp, short regExpLen)
  704. {
  705.     unsigned char *p, *q, *qEnd, *e;
  706.     unsigned char equivs[256];
  707.     short numEquiv;
  708.     
  709.     p = (unsigned char*)pattern;
  710.     q = (unsigned char*)regExp;
  711.     qEnd = q + regExpLen;
  712.     
  713.     if (q >= qEnd) goto exit;
  714.     *q++ = '*';
  715.     while (*p != 0) {
  716.         GetEquivalentCharacters(*p, equivs, &numEquiv);
  717.         if (numEquiv > 1) {
  718.             if (q >= qEnd) goto exit;
  719.             *q++ = '[';
  720.         }
  721.         e = equivs;
  722.         while (*e != 0) {
  723.             if (*e == '*' || *e == '?' || *e == '\\' || *e == '[' || *e == ']') {
  724.                 if (q >= qEnd) goto exit;
  725.                 *q++ = '\\';
  726.             }
  727.             if (q >= qEnd) goto exit;
  728.             *q++ = *e++;
  729.         }
  730.         if (numEquiv > 1) {
  731.             if (q >= qEnd) goto exit;
  732.             *q++ = ']';
  733.         }
  734.         p++;
  735.     }
  736.     if (q >= qEnd) goto exit;
  737.     *q++ = '*';
  738.     if (q >= qEnd) goto exit;
  739.     *q = 0;
  740.     MapMacToLatin1Ptr(regExp, (char*)q - regExp);
  741.     return;
  742.     
  743. exit:
  744.  
  745.     *(regExp + regExpLen - 1) = 0;
  746.     return;
  747. }
  748.  
  749.  
  750.  
  751. /*----------------------------------------------------------------------------
  752.     MatchPattern 
  753.     
  754.     Match a pattern.
  755.     
  756.     Entry:    pattern = substring for which we are searching, in Mac 8 bit
  757.                 character set.
  758.             string = string to be searched, in Latin-1 8 bit character set.
  759.  
  760.     Exit:    function result = true if string contains pattern.
  761. ----------------------------------------------------------------------------*/
  762.  
  763. static Boolean MatchPattern (char *pattern, char *string)
  764. {
  765.     CStr255 str;
  766.     
  767.     MapLatin1ToMacStr(string, str);
  768.     return MyIsASubstring(str, pattern);
  769. }
  770.  
  771.  
  772.  
  773. /*----------------------------------------------------------------------------
  774.     StartNNTP 
  775.     
  776.     Open a connection to the news server.
  777.     
  778.     Exit:    function result = error code.
  779. ----------------------------------------------------------------------------*/
  780.  
  781. OSErr StartNNTP (void)
  782. {
  783.     NntpStreamOptions options;
  784.     OSErr err = noErr;
  785.     
  786.     gCancel = false;
  787.     
  788.     MyICReadSharedPrefs(kICNNTPHost);
  789.     
  790.     while (true) {
  791.         gNewsStream = nil;
  792.         if (gPrefs.authAtStartup) {
  793.             err = GetAuthInfo(kStartupAuthDlg, nil);
  794.             if (err != noErr) return err;
  795.         }
  796.         err = DisplayStatusMessageNumber(kStrOpeningConnectionStatusMsg);
  797.         if (err != noErr) return err;
  798.         SetServerOptions(&options);
  799.         p2cstr(gPrefs.newsServerName);
  800.         err = NntpOpen((char*)gPrefs.newsServerName, &options, &gNewsStream);
  801.         c2pstr((char*)gPrefs.newsServerName);
  802.         if (gPrefs.authAtStartup && err == nntpAuthFailedErr) {
  803.             *gPrefs.authPassword = 0;
  804.             MyICWriteSharedPrefs(kICNewsAuthPassword);
  805.             gNewsStream = nil;
  806.             ErrorMessageNumber(kStrAuthFailed);
  807.         } else if (err != noErr) {
  808.             goto exit;
  809.         } else {
  810.             break;
  811.         }
  812.     }
  813.     return noErr;
  814.     
  815. exit:
  816.  
  817.     err = NetworkError(gNewsStream, nil, err);
  818.     if (gNewsStream != nil) NntpClose(gNewsStream);
  819.     gNewsStream = nil;
  820.     return err;
  821. }
  822.  
  823.  
  824.  
  825. /*----------------------------------------------------------------------------
  826.     EndNNTP 
  827.     
  828.     Close the connection to the NNTP server.
  829. ----------------------------------------------------------------------------*/
  830.  
  831. void EndNNTP (void)
  832. {
  833.     if (gNewsStream != nil) NntpClose(gNewsStream);
  834.     gNewsStream = nil;
  835. }
  836.  
  837.  
  838.  
  839. /*----------------------------------------------------------------------------
  840.     GetGroupNames
  841.     
  842.     Get a list of group names from the server.
  843.     
  844.     Entry:    lastTime = 0: Fetch the entire full group list.
  845.             lastTime != 0: Fetch just the groups which have been created
  846.                 since lastTime - 36 hours.
  847.     
  848.     Exit:    function result = error code.
  849.             *strings = handle to C-format group name strings.
  850.             *numGroups = number of group names.
  851.     
  852.     The group names are mapped from Latin-1 to the Mac character set.
  853.     The group names are truncated to 127 characters.
  854.     Groups with status 'x' and '=' are filtered out (not returned).
  855. ----------------------------------------------------------------------------*/
  856.  
  857. OSErr GetGroupNames (unsigned long lastTime, Handle *strings, long *numGroups)
  858. {
  859.     OSErr err;
  860.     Handle theStrings;
  861.     long theNumGroups;
  862.     
  863.     if (lastTime != 0) {
  864.     
  865.         /* Just get groups added since "lastTime". Subtract 36 hours to compensate 
  866.            for clock drift, daylight savings time, and server and client in 
  867.            different time zones. */
  868.            
  869.         lastTime -= 60L*60L*36L;
  870.     }
  871.     
  872.     err = NntpGetGroupNames(gNewsStream, lastTime, "x=",
  873.         &theStrings, &theNumGroups);
  874.     if (err != noErr) return NetworkError(gNewsStream, nil, err);
  875.     
  876.     MapLatin1ToMacHandle(theStrings);
  877.     
  878.     #ifdef kDevelopmentVersion
  879.         #define kFactor 5
  880.         if (lastTime == 0) {
  881.             long len;
  882.             char *q, *p;
  883.             long i, j;
  884.             EventRecord ev;
  885.         
  886.             EventAvail(everyEvent, &ev);
  887.             if ((ev.modifiers & optionKey) != 0) {
  888.                 len = MyGetHandleSize(theStrings);
  889.                 err = MySetHandleSize(theStrings, (kFactor+1)*len);
  890.                 if (err != noErr) {
  891.                     MyDisposeHandle(theStrings);
  892.                     return err;
  893.                 }
  894.                 MyHLock(theStrings);
  895.                 p = *theStrings;
  896.                 q = *theStrings + kFactor*len;
  897.                 BlockMoveData(p, q, len);
  898.                 for (i = 0; i < theNumGroups; i++) {
  899.                     len = strlen(q);
  900.                     for (j = 0; j < kFactor; j++) {
  901.                         BlockMoveData(q, p, len);
  902.                         p += len;
  903.                         if (j > 0) *p++ = '0' + j;
  904.                         *p++ = 0;
  905.                     }
  906.                     q += len+1;
  907.                 }
  908.                 len = p - *theStrings;
  909.                 MyHUnlock(theStrings);
  910.                 MySetHandleSize(theStrings, len);
  911.                 theNumGroups = kFactor*theNumGroups;
  912.             }
  913.         }
  914.     #endif
  915.     
  916.     *strings = theStrings;
  917.     *numGroups = theNumGroups;
  918.     return noErr;
  919. }
  920.  
  921.  
  922.  
  923. /*----------------------------------------------------------------------------
  924.     GetArticle 
  925.     
  926.     Get one article from an NNTP server.
  927.     
  928.     Entry:    host = address of news server, or nil to use default server.
  929.             port = port number on news server if host not nil.
  930.             groupName = name of group, or nil if fetching by message id.
  931.             number = article number. Ignored if fetching by message id.
  932.             id = message id string, including < and > delimiters. Ignored
  933.                 if fetching by article number.
  934.             part = which part of the article to get:
  935.                 "ARTICLE": full article text, header and body.
  936.                 "HEAD": only article header.
  937.                 "BODY": only article body.
  938.             truncateIfAttachedFile = true to truncate the article if it
  939.                 contains an attached file. The BinHex or uuencode text
  940.                 for the attached file is not returned.
  941.             requireEncodedTextBeginLIne = true if BinHex or uuencode text
  942.                 must include special "begin" flag line.
  943.     
  944.     Exit:    function result = error code.
  945.             *text = handle to article text, or nil if article does not
  946.                 exit on server or group does not exist.
  947.             *textLength = length of article text.
  948.             *attachedFile = true if article contains an attached file which
  949.                 was truncated.
  950.                 
  951.     Backspace-underscore and underscore-backspace sequences are filtered out
  952.     of the article, as are all non-printable characters except for TAB and CR and FF.
  953.     Tabs are expanded to 8 column tab stops.
  954.     Trailing blank lines are deleted. 
  955.     CRLF line terminators are mapped to CR.
  956.     Leading double-period characters on lines are mapped to single periods.
  957.     8 bit characters are mapped from Latin-1.
  958. ----------------------------------------------------------------------------*/
  959.  
  960. OSErr GetArticle (char *host, short port,
  961.     char *groupName, long number, char *id, char *part, 
  962.     Boolean truncateIfAttachedFile, Boolean flagReqd, 
  963.     Handle *text, long *textLength, Boolean *attachedFile)
  964. {
  965.     long len, newLen, col, nSpace;
  966.     unsigned char **txt = nil;
  967.     unsigned char *p, *pEnd, *q, *r;
  968.     OSErr err = noErr;
  969.     NntpStreamRef stream = nil;
  970.     CStr255 hostAndPortString;
  971.     TTruncateAttachedFileInfo x;
  972.     
  973.     if (host == nil) {
  974.         stream = gNewsStream;
  975.     } else {
  976.         sprintf(hostAndPortString, "%s,%d", host, port);
  977.         err = NntpOpen(hostAndPortString, nil, &stream);
  978.         if (err != noErr) goto exit;
  979.     }
  980.     
  981.     if (truncateIfAttachedFile) {
  982.         x.pos = 0;
  983.         x.flagReqd = flagReqd;
  984.         err = NntpGetArticle(stream, groupName, number, id, part,
  985.              (Handle*)&txt, TruncateAttachedFile, (Ptr)&x);
  986.         if (err == netTruncatedErr) {
  987.             if (attachedFile != nil) *attachedFile = true;
  988.         } else {
  989.             if (attachedFile != nil) *attachedFile = false;
  990.             if (err != noErr) goto exit;
  991.         }
  992.     } else {
  993.         err = NntpGetArticle(stream, groupName, number, id, part,
  994.             (Handle*)&txt, nil, nil);
  995.         if (err != noErr) goto exit;
  996.     }
  997.     
  998.     if (host != nil) {
  999.         NntpClose(stream);
  1000.         stream = nil;
  1001.     }
  1002.  
  1003.     len = MyGetHandleSize(txt);
  1004.     
  1005.     for (p = *txt, pEnd = p + len, q = *txt; p < pEnd;) {
  1006.         if (p < pEnd-1 && ((*p == BS && *(p+1) == '_') || (*p == '_' && *(p+1) == BS))) {
  1007.             /* Filter underscore backspace and backspace underscore */
  1008.             p += 2;
  1009.         } else if (*p >= ' ' || *p == CR || *p == '\t' || *p == FF) {
  1010.             /* Copy printable character as is */
  1011.             *q++ = *p++;
  1012.         } else {
  1013.             /* Filter unprintable character */
  1014.             p++;
  1015.         }
  1016.     }
  1017.     
  1018.     /* Trim trailing blank lines */
  1019.     
  1020.     q--;
  1021.     while (q >= *txt && *q == CR) q--;
  1022.     q++;
  1023.     
  1024.     len = q - *txt;
  1025.     MySetHandleSize(txt, len);
  1026.     
  1027.     /* Map Latin1 */
  1028.     
  1029.     MapLatin1ToMacHandle((Handle)txt);
  1030.     
  1031.     /* Expand tabs. */
  1032.     
  1033.     newLen = len;
  1034.     r = *txt;
  1035.     for (p = *txt, pEnd = p + len; p < pEnd; p++) {
  1036.         if (*p == '\t') {
  1037.             col = p - r;
  1038.             nSpace = 8 - (col % 8);
  1039.             newLen += nSpace - 1;
  1040.             r = p+1;
  1041.         } else if (*p == CR) {
  1042.             r = p+1;
  1043.         }
  1044.     }
  1045.     if (newLen > len) {
  1046.         err = MySetHandleSize(txt, newLen);
  1047.         if (err != noErr) goto exit;
  1048.         BlockMoveData(*txt, *txt + newLen - len, len);
  1049.         r = *txt + newLen - len;;
  1050.         for (p = r, pEnd = p + len, q = *txt; p < pEnd; p++) {
  1051.             if (*p == '\t') {
  1052.                 col = p - r;
  1053.                 nSpace = 8 - (col % 8);
  1054.                 while (nSpace--) *q++ = ' ';
  1055.                 r = p+1;
  1056.             } else if (*p == CR) {
  1057.                 *q++ = *p;
  1058.                 r = p+1;
  1059.             } else {
  1060.                 *q++ = *p;
  1061.             }
  1062.         }
  1063.         len = newLen;
  1064.     }
  1065.     
  1066.     *text = (Handle)txt;
  1067.     *textLength = len;
  1068.     return noErr;
  1069.     
  1070. exit:
  1071.  
  1072.     if (err == nntpNoSuchGroupErr || err == nntpNoSuchArticleErr) {
  1073.         *text = nil;
  1074.         return noErr;
  1075.     } else {
  1076.         err = NetworkError(stream, host, err);
  1077.     }
  1078.     if (host != nil && stream != nil) NntpClose(stream);
  1079.     MyDisposeHandle(txt);
  1080.     return err;
  1081. }
  1082.  
  1083.  
  1084.  
  1085. /*----------------------------------------------------------------------------
  1086.     CopyArticleToFile 
  1087.     
  1088.     Get one article from the NNTP server and copy it to a file.
  1089.     
  1090.     Entry:    groupName = name of group, or nil if fetching by message id.
  1091.             number = article number. Ignored if fetching by message id.
  1092.             id = message id string, including < and > delimiters. Ignored
  1093.                 if fetching by article number.
  1094.             part = which part of the article to get:
  1095.                 "ARTICLE": full article text, header and body.
  1096.                 "HEAD": only article header.
  1097.                 "BODY": only article body.
  1098.             refNum = reference number of open file.
  1099.             truncateIfAttachedFile = true to truncate the article if it
  1100.                 contains an attached file. The BinHex or uuencode text
  1101.                 for the attached file is not copied to the file.
  1102.             flagReqd = true if BinHex or uuencode text
  1103.                 must include special "begin" flag line.
  1104.     
  1105.     Exit:    function result = error code.
  1106.             *fileKind = attached file kind, if !truncateIfAttachedFile.
  1107.                 
  1108.     Backspace-underscore and underscore-backspace sequences are filtered out
  1109.     of the article, as are all non-printable characters except for TAB and CR and FF.
  1110.     Tabs are expanded to 8 column tab stops.
  1111.     CRLF line terminators are mapped to CR.
  1112.     Leading double-period characters on lines are mapped to single periods.
  1113.     8 bit characters are mapped from Latin-1.
  1114. ----------------------------------------------------------------------------*/
  1115.  
  1116. OSErr CopyArticleToFile (char *groupName, long number, char *id, char *part, 
  1117.     short refNum, Boolean truncateIfAttachedFile, 
  1118.     Boolean flagReqd, TAttachedFileKind *fileKind)
  1119. {
  1120.     OSErr err = noErr;
  1121.     TCopyArticleToFileInfo x;
  1122.     
  1123.     x.fileBuf = x.fileBufAux = nil;
  1124.     x.fileRefNum = refNum;
  1125.     err = MyNewPtr(kFileBufLen, &x.fileBuf);
  1126.     if (err != noErr) goto exit;
  1127.     err = MyNewPtr(400, &x.fileBufAux);
  1128.     if (err != noErr) goto exit;
  1129.     x.fileBufAuxSize = 0;
  1130.     x.fileBufPos = 0;
  1131.     x.fileLinePos = 0;
  1132.     x.fileLastChar = 0;
  1133.     x.fileTruncate = truncateIfAttachedFile;
  1134.     x.fileTruncated = false;
  1135.     x.fileKind = kNoAttachedFile;
  1136.     x.flagReqd = flagReqd;
  1137.     
  1138.     err = NntpGetArticle(gNewsStream, groupName, number, id, part,
  1139.         nil, CopyArticleToFileChunkFunction, (Ptr)&x);
  1140.     if (err == netTruncatedErr) err = noErr;
  1141.     if (err != noErr) goto exit;
  1142.  
  1143.     if (!x.fileTruncated) {
  1144.         if (x.fileLastChar != 0) {
  1145.             *(x.fileBuf + x.fileBufPos) = x.fileLastChar;
  1146.             x.fileBufPos++;
  1147.         }
  1148.         if (x.fileBufPos > 0) err = FlushFileBuf(x.fileBufPos, &x);
  1149.         if (x.fileTruncate && x.fileBufAuxSize > 0) {
  1150.             err = MyFSWriteNoCache(x.fileRefNum, &x.fileBufAuxSize, x.fileBufAux, GiveTime);
  1151.             if (err != noErr) goto exit;
  1152.         }
  1153.     }
  1154.     
  1155.     *fileKind = x.fileKind;
  1156.     
  1157. exit:
  1158.     
  1159.     MyDisposePtr(x.fileBuf);
  1160.     MyDisposePtr(x.fileBufAux);
  1161.     x.fileBuf = nil;
  1162.     x.fileBufAux = nil;
  1163.     if (err == nntpNoSuchGroupErr || err == nntpNoSuchArticleErr) {
  1164.         *fileKind = kArtNotOnServer;
  1165.         return noErr;
  1166.     } else {
  1167.         return NetworkError(gNewsStream, nil, err);
  1168.     }
  1169. }
  1170.  
  1171.  
  1172.  
  1173. /*----------------------------------------------------------------------------
  1174.     PostArticle 
  1175.     
  1176.     Post an article.
  1177.     
  1178.     Entry:    text = handle to article text, including header.
  1179.             statusMsg = status message, C-format.
  1180.             
  1181.     Exit:    function result = error code.
  1182.             postIndeterminate = true if entire article sent, but error occured
  1183.                 or user canceled before final server response received. The
  1184.                 article may or may not have been posted successfully.
  1185. ----------------------------------------------------------------------------*/
  1186.  
  1187. OSErr PostArticle (Handle text, char *statusMsg, Boolean *postIndeterminate)
  1188. {
  1189.     OSErr err = noErr;
  1190.     NetServerErrInfo serverErrInfo;
  1191.     Boolean munged;
  1192.  
  1193.     err = NntpPostArticle(gNewsStream, text, true, postIndeterminate, &munged);
  1194.     if (err == nntpServerErr) {
  1195.         NntpGetServerErrInfo(gNewsStream, &serverErrInfo);
  1196.         if (serverErrInfo.responseCode != 480) goto exit;
  1197.         while (true) {
  1198.             err = GetAuthInfo(kPostAuthDlg, statusMsg);
  1199.             if (err != noErr) return err;
  1200.             ResetNewsServerOptions();
  1201.             err = NntpAuthorize(gNewsStream);
  1202.             if (err == nntpAuthFailedErr) {
  1203.                 *gPrefs.authPassword = 0;
  1204.                 MyICWriteSharedPrefs(kICNewsAuthPassword);
  1205.                 ErrorMessageNumber(kStrAuthFailed);
  1206.             } else if (err != noErr) {
  1207.                 goto exit;
  1208.             } else {
  1209.                 break;
  1210.             }
  1211.         }
  1212.         err = NntpPostArticle(gNewsStream, text, !munged, postIndeterminate, 
  1213.             &munged);
  1214.         if (err != noErr) goto exit;
  1215.     } else if (err != noErr) {
  1216.         goto exit;
  1217.     }
  1218.     return noErr;
  1219.  
  1220. exit:
  1221.  
  1222.     return NetworkError(gNewsStream, nil, err);
  1223. }
  1224.  
  1225.  
  1226.  
  1227. /*----------------------------------------------------------------------------
  1228.     GetGroupArticleRange 
  1229.     
  1230.     Query the NNTP server to get the current article range for a single group.
  1231.     
  1232.     Entry:    theGroup = pointer to group record.
  1233.     
  1234.     Exit:    function result = error code.
  1235.             *groupExits = true if group exists.
  1236.     
  1237.             theGroup->firstMess = first article in range.
  1238.             theGroup->lastMess = last article in range.
  1239.             theGroup->numUnread = number of articles in range.
  1240. ----------------------------------------------------------------------------*/
  1241.  
  1242. OSErr GetGroupArticleRange (TGroup *theGroup, Boolean *groupExists)
  1243. {
  1244.     CStr255 groupName;
  1245.     long first, last, count;
  1246.     OSErr err = noErr;
  1247.     
  1248.     *groupExists = true;
  1249.     strcpy(groupName, *gGroupNames + theGroup->nameOffset);
  1250.     err = NntpGetGroupInfo(gNewsStream, groupName, &first, &last, &count);
  1251.     if (err != noErr) goto exit;
  1252.     
  1253.     if (first == 0 && last == 0) {
  1254.         /* Special case empty group: set firstMess = lastMess + 1. */
  1255.         theGroup->firstMess = theGroup->lastMess + 1;
  1256.         err = AgeArticleCache(groupName, kMaxLong);
  1257.         if (err != noErr) return err;
  1258.     } else {
  1259.         if (first <= 0) first = 1;
  1260.         if (last < first) last = first - 1;
  1261.         theGroup->firstMess = first;
  1262.         theGroup->lastMess = last;
  1263.         err = AgeArticleCache(groupName, first);
  1264.         if (err != noErr) return err;
  1265.     } 
  1266.     theGroup->numUnread = theGroup->lastMess - theGroup->firstMess + 1;
  1267.     return noErr;
  1268.     
  1269. exit:
  1270.  
  1271.     if (err == nntpNoSuchGroupErr) {
  1272.         *groupExists = false;
  1273.         return noErr;
  1274.     } else {
  1275.         return NetworkError(gNewsStream, nil, err);
  1276.     }
  1277. }
  1278.  
  1279.  
  1280.  
  1281. /*----------------------------------------------------------------------------
  1282.     GetGroupArrayArticleRanges 
  1283.     
  1284.     Query the NNTP server to get current article range info for designated 
  1285.     groups in a group list array.
  1286.  
  1287.     Entry:    groupArray = handle to group array.
  1288.             numGroups = number of groups in group array.
  1289.  
  1290.             Each group in the array to be updated is marked with status='x'.
  1291.  
  1292.     Exit:    function result = error code.
  1293.             Deleted groups are marked with status='d'.
  1294. ----------------------------------------------------------------------------*/
  1295.  
  1296. OSErr GetGroupArrayArticleRanges (TGroup **groupArray, long numGroups)
  1297. {
  1298.     NntpGroupInfoHandle info = nil;
  1299.     NntpGroupInfoPtr q, qEnd;
  1300.     TGroup *p, *pEnd;
  1301.     long first, last;
  1302.     long numGroupsToUpdate = 0;
  1303.     CStr255 groupName;
  1304.     OSErr err = noErr;
  1305.     
  1306.     for (p = *groupArray, pEnd = p + numGroups; p < pEnd; p++)
  1307.         if (p->status == 'x') numGroupsToUpdate++;
  1308.         
  1309.     err = MyNewHandle(numGroupsToUpdate * sizeof(NntpGroupInfo), &info);
  1310.     if (err != noErr) goto exit;
  1311.     
  1312.     for (p = *groupArray, pEnd = p + numGroups, q = *info; p < pEnd; p++) {
  1313.         if (p->status == 'x') {
  1314.             q->offset = p->nameOffset;
  1315.             q++;
  1316.         }
  1317.     }
  1318.     
  1319.     err = NntpGetMultipleGroupInfo(gNewsStream, info, numGroupsToUpdate, gGroupNames);
  1320.     if (err != noErr) goto exit;
  1321.     
  1322.     MyHLock(info);
  1323.     for (q = *info, qEnd = q + numGroupsToUpdate, p = *groupArray; q < qEnd; q++) {
  1324.         while (p->status != 'x') p++;
  1325.         if (q->ok) {
  1326.             first = q->first;
  1327.             last = q->last;
  1328.             strcpy(groupName, *gGroupNames + q->offset);
  1329.             if (first == 0 && last == 0) {
  1330.                 /* Special case empty group: set firstMess = lastMess + 1. */
  1331.                 p->firstMess = p->lastMess + 1;
  1332.                 err = AgeArticleCache(groupName, kMaxLong);
  1333.                 if (err != noErr) goto exit;
  1334.             } else {
  1335.                 if (first <= 0) first = 1;
  1336.                 if (last < first) last = first - 1;
  1337.                 p->firstMess = first;
  1338.                 p->lastMess = last;
  1339.                 err = AgeArticleCache(groupName, first);
  1340.                 if (err != noErr) goto exit;
  1341.             } 
  1342.             p->numUnread = p->lastMess - p->firstMess + 1;
  1343.         } else {
  1344.             p->status = 'd';
  1345.         }
  1346.         p++;
  1347.     }
  1348.     MyDisposeHandle(info);
  1349.     return noErr;
  1350.     
  1351. exit:
  1352.  
  1353.     MyDisposeHandle(info);
  1354.     return NetworkError(gNewsStream, nil, err);
  1355. }
  1356.  
  1357.  
  1358.  
  1359. /*----------------------------------------------------------------------------
  1360.     GetHeaders 
  1361.     
  1362.     Get header lines from the news server.
  1363.  
  1364.     Entry:    groupName = the group name.
  1365.             headerName = the header name.
  1366.             first = first article number.
  1367.             last = last article number.
  1368.             
  1369.     Exit:    function result = error code.
  1370.             *headers = handle to array of THeader records, or nil if
  1371.                 group does not exist.
  1372.             *strings = handle to header strings.
  1373.             *numHeaders = number of headers.
  1374. ----------------------------------------------------------------------------*/
  1375.  
  1376. OSErr GetHeaders (char *groupName, char *headerName, long first, long last, 
  1377.     THeader ***headers, Handle *strings, long *numHeaders)
  1378. {
  1379.     OSErr err;
  1380.     NntpHeaderInfoHandle info;
  1381.     Handle str;
  1382.     long num;
  1383.     unsigned char *p, *pEnd, *q;
  1384.     
  1385.     err = NntpGetHeaders(gNewsStream, groupName, first, last, headerName,
  1386.         nil, nil, nil, &info, &str, &num);
  1387.     if (err != noErr) goto exit;
  1388.     
  1389.     /* Filter unprintable characters. */
  1390.     
  1391.     p = (unsigned char*)*str;
  1392.     pEnd = p + MyGetHandleSize(str);
  1393.     while (p < pEnd) {
  1394.         q = p;
  1395.         while (*p != 0) {
  1396.             if (*p >= ' ') {
  1397.                 *q++ = *p++;
  1398.             } else {
  1399.                 p++;
  1400.             }
  1401.         }
  1402.         *q = 0;
  1403.         p++;
  1404.     }
  1405.     
  1406.     MapLatin1ToMacHandle(str);
  1407.     
  1408.     *headers = (THeader**)info;
  1409.     *strings = str;
  1410.     *numHeaders = num;
  1411.     return noErr;
  1412.     
  1413. exit:
  1414.  
  1415.     if (err == nntpNoSuchGroupErr) {
  1416.         *headers = nil;
  1417.         return noErr;
  1418.     } else {
  1419.         return NetworkError(gNewsStream, nil, err);
  1420.     }
  1421. }
  1422.  
  1423.  
  1424.  
  1425. /*----------------------------------------------------------------------------
  1426.     SearchHeaders 
  1427.     
  1428.     Search header lines and returns matches. The search is for any 
  1429.     substring and is case-insensitive. The XPAT command is used if the
  1430.     server supports it and the "use XPAT" preference is turned on. 
  1431.     Otherwise, we do it the hard way.
  1432.  
  1433.     Entry:    groupName = the group name.
  1434.             headerName = the header name.
  1435.             first = first article number.
  1436.             last = last article number.
  1437.             pattern = search string. WARNING: This string is modified
  1438.                 by the function.
  1439.             
  1440.     Exit:    function result = error code.
  1441.             *headers = handle to array of THeader records, or nil if
  1442.                 group does not exist.
  1443.             *numHeaders = number of headers.
  1444. ----------------------------------------------------------------------------*/
  1445.  
  1446. OSErr SearchHeaders (char *groupName, char *headerName, long first, long last,
  1447.     char *pattern, THeader ***headers, short *numHeaders)
  1448. {
  1449.     OSErr err;
  1450.     NntpHeaderInfoHandle info;
  1451.     Handle str = nil;
  1452.     long num;
  1453.  
  1454.     err = NntpGetHeaders(gNewsStream, groupName, first, last, headerName,
  1455.         pattern, BuildXPAT, MatchPattern, &info, &str, &num);
  1456.     if (err != noErr) goto exit;
  1457.  
  1458.     MyDisposeHandle(str);
  1459.     *headers = (THeader**)info;
  1460.     *numHeaders = num;
  1461.     return noErr;
  1462.     
  1463. exit:
  1464.  
  1465.     MyDisposeHandle(str);
  1466.     if (err == nntpNoSuchGroupErr) {
  1467.         *headers = nil;
  1468.         return noErr;
  1469.     } else {
  1470.         return NetworkError(gNewsStream, nil, err);
  1471.     }
  1472. }
  1473.  
  1474.  
  1475.  
  1476. /*----------------------------------------------------------------------------
  1477.     DoGetServerInfo 
  1478.     
  1479.     Handle the "Get Server Info" command.
  1480.     
  1481.     Exit:    function result = error code.
  1482. ----------------------------------------------------------------------------*/
  1483.  
  1484. OSErr DoGetServerInfo (void)
  1485. {
  1486.     char str[1000];
  1487.     char *p, *pEnd, *q;
  1488.     Handle serverHelpResponse = nil;
  1489.     Handle serverInfoText = nil;
  1490.     Str255 dateStr, timeStr, versStr, title;
  1491.     CStr255 addrStr;
  1492.     unsigned long rawSecs;
  1493.     unsigned long ipAddr;
  1494.     CStr255 helloMsg;
  1495.     CStr255 fmt;
  1496.     OSErr err = noErr;
  1497.     WindowPtr wind;
  1498.     NetServerErrInfo serverErrInfo;
  1499.     
  1500.     MyICReadSharedPrefs(kICNNTPHost);
  1501.  
  1502.     GetPString(kStrServerInfoWindTitle, title);
  1503.     if (CheckTextWindowAlreadyOpen(title)) return noErr;
  1504.  
  1505.     HiliteMenu(0);
  1506.  
  1507.     GetDateTime(&rawSecs);
  1508.     IUDateString(rawSecs, abbrevDate, dateStr);
  1509.     IUTimeString(rawSecs, false, timeStr);
  1510.     err = GetVersionString(versStr);
  1511.     if (err != noErr) goto exit;
  1512.     NntpGetIPAddr(gNewsStream, &ipAddr);
  1513.     NntpGetHello(gNewsStream, helloMsg);
  1514.     MapLatin1ToMacStr(helloMsg, helloMsg);
  1515.     sprintf(addrStr, "%ld.%ld.%ld.%ld", (ipAddr >> 24) & 0xff, (ipAddr >> 16) & 0xff, 
  1516.         (ipAddr >> 8) & 0xff, ipAddr & 0xff);
  1517.     err = NntpGetHelp(gNewsStream, &serverHelpResponse);
  1518.     if (err != noErr) goto exit;
  1519.     NntpGetServerErrInfo(gNewsStream, &serverErrInfo);
  1520.     MapLatin1ToMacStr(serverErrInfo.response, serverErrInfo.response);
  1521.     MapLatin1ToMacHandle(serverHelpResponse);
  1522.     GetCString(kStrServerInfoFmt, fmt);
  1523.     p2cstr(dateStr);
  1524.     p2cstr(timeStr);
  1525.     p2cstr(versStr);
  1526.     p2cstr(gPrefs.newsServerName);
  1527.     sprintf(str, fmt, dateStr, timeStr, versStr, gPrefs.newsServerName, 
  1528.         addrStr, helloMsg, serverErrInfo.response);
  1529.     c2pstr((char*)gPrefs.newsServerName);
  1530.  
  1531.     p = *serverHelpResponse + MyGetHandleSize(serverHelpResponse) - 1;
  1532.     while (p > *serverHelpResponse && *p == CR) p--;
  1533.     MySetHandleSize(serverHelpResponse, p - *serverHelpResponse + 1);
  1534.     
  1535.     for (p = *serverHelpResponse, pEnd = p + MyGetHandleSize(serverHelpResponse);
  1536.         p < pEnd;
  1537.         p++)
  1538.     {
  1539.         if (*p == '<') {
  1540.             q = p+1;
  1541.             while (q < pEnd && *q != '>' && *q != '@' && *q != CR) q++;
  1542.             if (q < pEnd && *q == '@') {
  1543.                 q++;
  1544.                 while (q < pEnd && *q != '>' && *q != CR) q++;
  1545.                 if (q < pEnd && *q == '>') {
  1546.                     *p = ' ';
  1547.                     *q = ' ';
  1548.                     break;
  1549.                 }
  1550.             }
  1551.         }
  1552.     }
  1553.         
  1554.     err = MyPtrToHand(str, &serverInfoText, strlen(str));
  1555.     if (err != noErr) goto exit;
  1556.     err = MyHandAndHand(serverHelpResponse, serverInfoText);
  1557.     if (err != noErr) goto exit;
  1558.     MyDisposeHandle(serverHelpResponse);
  1559.     serverHelpResponse = nil;
  1560.     err = MakeNewTextWindow(title, 0, nil, serverInfoText, &wind);
  1561.     if (err != noErr) goto exit;
  1562.     MyDisposeHandle(serverInfoText);
  1563.     return noErr;
  1564.     
  1565. exit:
  1566.  
  1567.     MyDisposeHandle(serverHelpResponse);
  1568.     MyDisposeHandle(serverInfoText);
  1569.     return NetworkError(gNewsStream, nil, err);
  1570. }
  1571.  
  1572.  
  1573.  
  1574. /*----------------------------------------------------------------------------
  1575.     ResetNewsServerOptions 
  1576.     
  1577.     Reset the server options on the news server stream. This function must
  1578.     be called whenever the options change (e.g., when the user changes them
  1579.     in the prefs dialog).
  1580. ----------------------------------------------------------------------------*/
  1581.  
  1582. void ResetNewsServerOptions (void)
  1583. {
  1584.     NntpStreamOptions options;
  1585.  
  1586.     SetServerOptions(&options);
  1587.     if (gNewsStream != nil) NntpSetStreamOptions(gNewsStream, &options);
  1588. }
  1589.  
  1590.  
  1591.  
  1592. /*----------------------------------------------------------------------------
  1593.     Reauthenticate 
  1594.     
  1595.     Reauthenticate the user after a change in username or password.
  1596.     
  1597.     Exit:    function result = error code.
  1598. ----------------------------------------------------------------------------*/
  1599.  
  1600. OSErr ReAuthenticate (void)
  1601. {
  1602.     NntpStreamRef oldStream;
  1603.     OSErr err = noErr;
  1604.  
  1605.     if (gNewsStream == nil) return noErr;
  1606.     if (!gPrefs.authAtStartup) {
  1607.         NntpAbort(gNewsStream);
  1608.     } else {
  1609.         oldStream = gNewsStream;
  1610.         gNewsStream = nil;
  1611.         err = StartNNTP();
  1612.         if (err == noErr) {
  1613.             NntpClose(oldStream);
  1614.         } else {
  1615.             gNewsStream = oldStream;
  1616.         }
  1617.     }
  1618.     return err;
  1619. }
  1620.